package chapter4
{
	import com.adobe.utils.*;
	
	import flash.display.*;
	import flash.display.Stage;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.events.*;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	
	import utils.DisplayMolehill;
	import utils.IRender;
	
	public class Chapter4Demp extends DisplayMolehill implements IRender
	{
		// used by the FPS display
		private var fpsLast:uint = getTimer();
		private var fpsTicks:uint = 0;
		private var fpsTf:TextField;
		
		// constants used during inits
		private const swfWidth:int = 640;
		private const swfHeight:int = 480;
		private const textureSize:int = 512;
		
		// the 3d graphics window on the stage
		private var context3D:Context3D;
		// the compiled shaders used to render our mesh
		private var shaderProgram1:Program3D;
		private var shaderProgram2:Program3D;
		private var shaderProgram3:Program3D;
		private var shaderProgram4:Program3D;
		
		// the uploaded vertexes used by our mesh
		private var vertexBuffer:VertexBuffer3D;
		// the uploaded indexes of each vertex of the mesh
		private var indexBuffer:IndexBuffer3D;
		// the data that defines our 3d mesh model
		private var meshVertexData:Vector.<Number>;
		// the indexes that define what data is used by each vertex
		private var meshIndexData:Vector.<uint>;
		
		// matrices that affect the mesh location and camera angles
		private var projectionmatrix:PerspectiveMatrix3D =
			new PerspectiveMatrix3D();
		private var modelmatrix:Matrix3D = new Matrix3D();
		private var viewmatrix:Matrix3D = new Matrix3D();
		private var modelViewProjection:Matrix3D = new Matrix3D();
		
		// a simple frame counter used for animation
		private var t:Number = 0;
		// a reusable loop counter
		private var looptemp:int = 0;
		
		/* TEXTURE: Pure AS3 and Flex version:
		* if you are using Adobe Flash CS5
		* comment out the next two lines of code */
		[Embed (source = "assets/texture.jpg")]
		private var myTextureBitmap:Class;
		private var myTextureData:Bitmap = new myTextureBitmap();
		
		// The Stage3d Texture that uses the above myTextureData
		private var myTexture:Texture;
		
		public function Chapter4Demp(stage:Stage)
		{
			super(stage);
			
			// add some text labels
			initGUI();
			// and request a context3D from Stage3d
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}
		
		private function initGUI():void
		{
			// a text format descriptor used by all gui labels
			var myFormat:TextFormat = new TextFormat();
			myFormat.color = 0xFFFFFF;
			myFormat.size = 13;
			// create an FPSCounter that displays the framerate on screen
			fpsTf = new TextField();
			fpsTf.x = 0;
			fpsTf.y = 0;
			fpsTf.selectable = false;
			fpsTf.autoSize = TextFieldAutoSize.LEFT;
			fpsTf.defaultTextFormat = myFormat;
			fpsTf.text = "Initializing Stage3d...";
			stage.addChild(fpsTf);
			// add some labels to describe each shader
			var label1:TextField = new TextField();
			label1.x = 100;
			label1.y = 180;
			label1.selectable = false;
			label1.autoSize = TextFieldAutoSize.LEFT;
			label1.defaultTextFormat = myFormat;
			label1.text = "Shader 1: Textured";
			stage.addChild(label1);
			
			var label2:TextField = new TextField();
			label2.x = 400;
			label2.y = 180;
			label2.selectable = false;
			label2.autoSize = TextFieldAutoSize.LEFT;
			label2.defaultTextFormat = myFormat;
			label2.text = "Shader 2: Vertex RGB";
			stage.addChild(label2);
			
			var label3:TextField = new TextField();
			label3.x = 80;
			label3.y = 440;
			label3.selectable = false;
			label3.autoSize = TextFieldAutoSize.LEFT;
			label3.defaultTextFormat = myFormat;
			label3.text = "Shader 3: Vertex RGB + Textured";
			stage.addChild(label3);
			
			var label4:TextField = new TextField();
			label4.x = 340;
			label4.y = 440;
			label4.selectable = false;
			label4.autoSize = TextFieldAutoSize.LEFT;
			label4.defaultTextFormat = myFormat;
			label4.text = "Shader 4: Textured + setProgramConstants";
			stage.addChild(label4);
		}
		
		private function onContext3DCreate(event:Event):void{
			// Obtain the current context
			var t:Stage3D = event.target as Stage3D;
			context3D = t.context3D;
			if (context3D == null)
			{
				// Currently no 3d context is available (error!)
				return;
			}
			trace(context3D.driverInfo);
			
			// Disabling error checking will drastically improve performance.
			// If set to true, Flash sends helpful error messages regarding
			// AGAL compilation errors, uninitialized program constants, etc.
			context3D.enableErrorChecking = true;
			// Initialize our mesh data
			initData();
			
			// The 3d back buffer size is in pixels
			context3D.configureBackBuffer(swfWidth, swfHeight, 0, true);
			// assemble all the shaders we need
			initShaders();
			
			// upload the mesh indexes
			indexBuffer = context3D.createIndexBuffer(meshIndexData.length);
			indexBuffer.uploadFromVector(meshIndexData, 0, meshIndexData.length);
			
			// upload the mesh vertex data
			// since our particular data is
			// x, y, z, u, v, nx, ny, nz, r, g, b, a
			// each vertex uses 12 array elements
			vertexBuffer = context3D.createVertexBuffer(meshVertexData.length/12, 12);
			vertexBuffer.uploadFromVector(meshVertexData, 0,meshVertexData.length/12);
			
			myTexture = context3D.createTexture(textureSize, textureSize,Context3DTextureFormat.BGRA, false);
			myTexture.uploadFromBitmapData(processBitmap(myTextureData.bitmapData), 0);
			var mip:BitmapData = myTextureData.bitmapData;
			var texwidth:int = myTextureData.bitmapData.width;
			var n:int = 0;
			while (texwidth > 1) {
				texwidth /= 2;
				n++;
				var tex2:BitmapData = new BitmapData(
					texwidth, texwidth, true, 0);
				var mat:Matrix = new Matrix();
				mat.scale(0.5, 0.5);
				tex2.draw(mip, mat, null, null, null, true);
				myTexture.uploadFromBitmapData(tex2, n);
				mip = tex2;
			}
			
			// create projection matrix for our 3D scene
			projectionmatrix.identity();
			// 45 degrees FOV, 640/480 aspect ratio, 0.1=near, 100=far
			projectionmatrix.perspectiveFieldOfViewRH(
				45.0, swfWidth / swfHeight, 0.01, 100.0);
			// create a matrix that defines the camera location
			viewmatrix.identity();
			// move the camera back a little so we can see the mesh
			viewmatrix.appendTranslation(0,0,-10);
			
			this.dispatchEvent(new Event(Event.COMPLETE));
		}
		
		private function initShaders():void{
			// A simple vertex shader which does a 3D transformation
			// for simplicity, it is used by all four shaders
			var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble
			(
				Context3DProgramType.VERTEX,
				// 4x4 matrix multiply to get camera angle
				"m44 op, va0, vc0\n" +
				// tell fragment shader about XYZ
				"mov v0, va0\n" +
				// tell fragment shader about UV
				"mov v1, va1\n" +
				// tell fragment shader about RGBA
				"mov v2, va2\n"
			);
			
			// textured using UV coordinates
			var fragmentShaderAssembler1:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler1.assemble
			(
				Context3DProgramType.FRAGMENT,
				// grab the texture color from texture 0
				// and uv coordinates from varying register 1
				// and store the interpolated value in ft0
				"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
				// move this value to the output color
				"mov oc, ft0\n"
			);
			
			// no texture, RGBA from the vertex buffer data
			var fragmentShaderAssembler2:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler2.assemble
			(
				Context3DProgramType.FRAGMENT,
				// grab the color from the v2 register
				// which was set in the vertex program
				//"sub ft0, v2, fc1\n" +
				"mov oc, v2\n"
			);
			
			// textured using UV coordinates AND colored by vertex RGB
			var fragmentShaderAssembler3:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler3.assemble
			(
				Context3DProgramType.FRAGMENT,
				// grab the texture color from texture 0
				// and uv coordinates from varying register 1
				"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
				// multiply by the value stored in v2 (the vertex rgb)
				"mul ft1, v2, ft0\n" +
				// move this value to the output color
				"mov oc, ft1\n"
			);
			
			// textured using UV coordinates and
			// tinted using a fragment constant
			var fragmentShaderAssembler4:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler4.assemble
			(
				Context3DProgramType.FRAGMENT,
				// grab the texture color from texture 0
				// and uv coordinates from varying register 1
				"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
				// multiply by the value stored in fc0
				"mul ft1, fc0, ft0\n" +
				// move this value to the output color
				"mov oc, ft1\n"
			);
			
			// combine shaders into a program which we then upload to the GPU
			shaderProgram1 = context3D.createProgram();
			shaderProgram1.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler1.agalcode);
			
			shaderProgram2 = context3D.createProgram();
			shaderProgram2.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler2.agalcode);
			
			shaderProgram3 = context3D.createProgram();
			shaderProgram3.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler3.agalcode);
			
			shaderProgram4 = context3D.createProgram();
			shaderProgram4.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler4.agalcode);
			
		}
		
		private function initData():void
		{
			// Defines which vertex is used for each polygon
			// In this example a square is made from two triangles
			meshIndexData = Vector.<uint>
				([
					0, 1, 2, 0, 2, 3,
				]);
			// Raw data used for each of the 4 vertexes
			// Position XYZ, texture coord UV, normal XYZ, vertex RGBA
			meshVertexData = Vector.<Number>
				([
					//X, Y, Z, U, V, nX, nY, nZ, R, G, B, A
					-1, -1, 1, 0.25, 0.25, 0, 0, 1, 1.0,0.0,0.0,1.0,
					1, -1, 1, 0.75, 0.25, 0, 0, 1, 0.0,1.0,0.0,1.0,
					1, 1, 1, 1, 1, 0, 0, 1, 0.0,0.0,1.0,1.0,
					-1, 1, 1, 0, 1, 0, 0, 1, 1.0,1.0,1.0,1.0
				]);
		}
		public function render():void
		{
			// clear scene before rendering is mandatory
			context3D.clear(0,0,0);
			// rotate more next frame
			t += 2.0;
			// loop through each mesh we want to draw
			for (looptemp = 0; looptemp < 4; looptemp++)
			{
				// clear the transformation matrix to 0,0,0
				modelmatrix.identity();
				// each mesh has a different texture,
				// shader, position and spin speed
				switch(looptemp)
				{
					case 0:
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram1 );
						modelmatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.6, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(-3, 3, 0);
						break;
					case 1:
						context3D.setTextureAt(0, null);
						context3D.setProgram ( shaderProgram2 );
						modelmatrix.appendRotation(t*-0.2, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.4, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(3, 3, 0);
						break;
					case 2:
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram3 );
						modelmatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*-0.2, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*0.3, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(-3, -3, 0);
						break;
					case 3:
						context3D.setProgramConstantsFromVector(
							Context3DProgramType.FRAGMENT, 0, Vector.<Number>
							([ 1, Math.abs(Math.cos(t/50)), 0, 1 ]) );
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram4 );
						modelmatrix.appendRotation(t*0.3, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.3, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*-0.3, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(3, -3, 0);
						break;
				}
				// clear the matrix and append new angles
				modelViewProjection.identity();
				modelViewProjection.append(modelmatrix);
				modelViewProjection.append(viewmatrix);
				modelViewProjection.append(projectionmatrix);
				// pass our matrix data to the shader program
				context3D.setProgramConstantsFromMatrix(
					Context3DProgramType.VERTEX,
					0, modelViewProjection, true );
				//Above, we fill the AGAL register vc0 with our model view projection matrix:
				// associate the vertex data with current shader program
				// position
				context3D.setVertexBufferAt(0, vertexBuffer, 0,
					Context3DVertexBufferFormat.FLOAT_3);
				// tex coord
				context3D.setVertexBufferAt(1, vertexBuffer, 3,
					Context3DVertexBufferFormat.FLOAT_2);
				// vertex rgba
				context3D.setVertexBufferAt(2, vertexBuffer, 8,
					Context3DVertexBufferFormat.FLOAT_4);
				// finally draw the triangles
				context3D.drawTriangles(
					indexBuffer, 0, meshIndexData.length/3);
			}
			context3D.present();
			// update the FPS display
			fpsTicks++;
			var now:uint = getTimer();
			var delta:uint = now - fpsLast;
			// only update the display once a second
			if (delta >= 1000)
			{
				var fps:Number = fpsTicks / delta * 1000;
				fpsTf.text = fps.toFixed(1) + " fps";
				fpsTicks = 0;
				fpsLast = now;
			}
			
			
		}
		private function processBitmap(bitmapdata:BitmapData):BitmapData{
			var shape:Shape = new Shape;
			shape.graphics.beginFill(0xff0000,0.5);
			shape.graphics.drawRect(0,0,256,256);
			shape.graphics.endFill();
			var ma:Matrix = new Matrix;
			ma.translate(128,128);
			bitmapdata.draw(shape,ma);
			return bitmapdata;
			/*var bitmap:Bitmap = new Bitmap(bitmapdata);
			stage.addChild(bitmap);*/
		}
	}
}